package com.bossly.metro.Views;

import java.util.ArrayList;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.GestureDetector.OnGestureListener;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ZoomButtonsController;
import android.widget.ZoomButtonsController.OnZoomListener;

import com.bossly.metro.Metropoliten;
import com.bossly.metro.R;
import com.bossly.metro.dom.Station;

public class MetroView extends View implements OnZoomListener,
		OnGestureListener {
	private Drawable m_map = null;
	private ZoomButtonsController m_zoom;

	int[] maps = new int[] { R.drawable.kiev, R.drawable.harkov,
			R.drawable.dnipropetrovsk, R.drawable.kryvy_rih };

	private int m_mapX = 0;
	private int m_mapY = 0;
	private int m_mapWidth = 0;
	private int m_mapHeight = 0;
	private float m_scale = 1f;
	private float m_max_scale_out = 1f;
	private float m_scale_offset = 0.1f;
	private Metropoliten m_metro = null;
	private Drawable m_train = null;

	private GestureDetector m_gesture;
	private StationSelectListener m_handler;
	private TrainAnimationThread train_anim;

	public MetroView(Context context) {
		super(context);
	}

	public MetroView(Context context, AttributeSet attrs) {
		super(context, attrs);
		// TODO Auto-generated constructor stub
	}

	public MetroView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		// TODO Auto-generated constructor stub
	}

	@Override
	protected void onAttachedToWindow() {
		m_zoom = new ZoomButtonsController(this);
		m_zoom.setFocusable(true);
		m_zoom.setAutoDismissed(true);
		m_zoom.setZoomInEnabled(true);
		m_zoom.setZoomOutEnabled(true);

		m_zoom.setOnZoomListener(this);

		m_gesture = new GestureDetector(this);

		// TODO Auto-generated method stub
		super.onAttachedToWindow();
	}

	@Override
	protected void onDetachedFromWindow() {
		if (m_zoom != null) {
			m_zoom.setAutoDismissed(false);
			m_zoom.setVisible(false);
		}

		// TODO Auto-generated method stub
		super.onDetachedFromWindow();
	}

	public void setMap(int index) {
		Drawable drawable = getResources().getDrawable(maps[index - 1]);

		if (drawable != null) {
			m_map = drawable;
			m_mapWidth = m_map.getIntrinsicWidth();
			m_mapHeight = m_map.getIntrinsicHeight();
			m_map.setBounds(0, 0, m_mapWidth, m_mapHeight);

			m_scale = Math.min((float) getWidth() / (float) m_mapWidth,
					(float) getHeight() / (float) m_mapHeight);
			m_max_scale_out = m_scale;
			m_scale_offset = (1f - m_max_scale_out) / 5f;

			invalidate();
		}
	}

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		// if( m_map != null )
		// {
		// int w = m_map.getIntrinsicWidth();
		// int h = m_map.getIntrinsicHeight();
		//
		// setMeasuredDimension( resolveSize( w, widthMeasureSpec ),
		// resolveSize( h,
		// heightMeasureSpec ) );
		// }
		// else
		{
			super.onMeasure(widthMeasureSpec, heightMeasureSpec);
		}
	}

	@Override
	protected void onSizeChanged(int w, int h, int oldw, int oldh) {
		if (m_map != null) {
			m_scale = Math.min((float) w / (float) m_mapWidth, (float) h
					/ (float) m_mapHeight);
			m_max_scale_out = m_scale;
			m_scale_offset = (1f - m_max_scale_out) / 5f;
			m_zoom.setZoomOutEnabled(false);
		}

		// TODO Auto-generated method stub
		super.onSizeChanged(w, h, oldw, oldh);
	}

	@Override
	protected void onDraw(Canvas canvas) {
		if (m_map != null) {
			canvas.save();

			int x = m_mapX;
			int y = m_mapY;

			int width = (int) (m_scale * m_map.getIntrinsicWidth());
			int height = (int) (m_scale * m_map.getIntrinsicHeight());

			int swidth = (int) (width - getWidth());
			int sheight = (int) (height - getHeight());

			if (swidth <= 0)
				x = swidth / 2;
			else
				x = (int) Math.min(Math.max(0, x), swidth);

			if (sheight <= 0)
				y = sheight / 2;
			else
				y = (int) Math.min(Math.max(0, y), sheight);

			canvas.translate(-x, -y);

			Rect rc = canvas.getClipBounds();
			rc.inset(getPaddingLeft(), getPaddingTop());
			canvas.clipRect(rc);

			m_map.setBounds(getPaddingLeft(), getPaddingTop(), width
					- getPaddingRight(), height - getPaddingBottom());
			m_map.draw(canvas);

			if (m_train != null) {
				m_train.draw(canvas);
			}

			canvas.restore();
		}

		// TODO Auto-generated method stub
		super.onDraw(canvas);
	}

	@Override
	public boolean dispatchTouchEvent(MotionEvent event) {
		m_gesture.onTouchEvent(event);

		super.dispatchTouchEvent(event);

		return true;
	}

	@Override
	public void onVisibilityChanged(boolean arg0) {
		// TODO Auto-generated method stub

	}

	@Override
	public void onZoom(boolean zoomIn) {
		if (zoomIn) {
			m_scale = Math.min(1f, m_scale + m_scale_offset);
		} else {
			m_scale = Math.max(m_max_scale_out, m_scale - m_scale_offset);
		}

		m_zoom.setZoomInEnabled(m_scale < 1f);
		m_zoom.setZoomOutEnabled(m_scale > m_max_scale_out);

		invalidate();
	}

	@Override
	public boolean onDown(MotionEvent arg0) {
		return true;
	}

	@Override
	public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
			float velocityY) {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public void onLongPress(MotionEvent e) {
		// TODO Auto-generated method stub

	}

	@Override
	public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
			float distanceY) {
		int width = (int) (m_scale * m_map.getIntrinsicWidth() - getWidth());
		int height = (int) (m_scale * m_map.getIntrinsicHeight() - getHeight());

		if (width > 0)
			m_mapX = (int) Math.min(Math.max(0, m_mapX + distanceX), width);

		if (height > 0)
			m_mapY = (int) Math.min(Math.max(0, m_mapY + distanceY), height);

		invalidate();

		return true;
	}

	@Override
	public void onShowPress(MotionEvent e) {
		// TODO Auto-generated method stub

	}

	@Override
	public boolean onSingleTapUp(MotionEvent e) {
		Station station = null;

		if (m_metro != null && m_handler != null && isEnabled()) {
			int x = (int) e.getX();
			int y = (int) e.getY();

			int width = (int) (m_scale * m_map.getIntrinsicWidth());
			int height = (int) (m_scale * m_map.getIntrinsicHeight());

			int swidth = (int) (width - getWidth());
			int sheight = (int) (height - getHeight());

			if (swidth <= 0)
				x += swidth / 2;

			if (sheight <= 0)
				y += sheight / 2;

			x = (int) ((float) (x + m_mapX) / m_scale);
			y = (int) ((float) (y + m_mapY) / m_scale);

			station = m_metro.hitStation(x, y);
		}

		if (station == null) {
			m_zoom.setVisible(true);
		} else {
			m_handler.onStationSelected(station);
		}

		return true;
	}

	public void setOnStationClick(StationSelectListener listener) {
		m_handler = listener;
	}

	public void setMetropolite(Metropoliten metro) {
		m_metro = metro;
	}

	public boolean isTutorial() {
		if (train_anim != null) {
			return train_anim.isAlive();
		}

		return false;
	}

	public void showTutorial(ArrayList<Station> way, final Handler handle) {
		// show itself
		setVisibility(VISIBLE);
		setEnabled(false);

		Rect rcRect = getBounds(way);

		// 1. zoom between points
		// zoomBetween( rcRect );

		m_train = getResources().getDrawable(R.drawable.train);

		m_mapX = 0;
		m_mapY = 0;
		m_scale = 1f;

		// 2. zoom to point1

		if (train_anim != null)
			train_anim.stopAnimation();

		// 3. move train between points to last point
		train_anim = new TrainAnimationThread(way, new Handler() {
			public void handleMessage(Message msg) {
				if (msg.what == 1) {
					handle.obtainMessage().sendToTarget();
					setVisibility(View.INVISIBLE);
				} else {
					int x = msg.arg1 - m_train.getIntrinsicWidth() / 2;
					int y = msg.arg2 - m_train.getIntrinsicHeight() / 2;
					m_mapX = x - getWidth() / 2;
					m_mapY = y - getHeight() / 2;

					m_train.setBounds(x, y, x + m_train.getIntrinsicWidth(), y
							+ m_train.getIntrinsicHeight());
					invalidate();
				}
			};
		});

		train_anim.start();
	}

	private Rect getBounds(ArrayList<Station> way) {
		Rect rc = null;

		for (int i = 0, length = way.size(); i < length; i++) {
			Point pt = way.get(i).getSreenLocation();

			if (rc != null)
				rc.union(pt.x, pt.y);
			else
				rc = new Rect(pt.x, pt.y, pt.x, pt.y);
		}

		return rc;
	}

	private void zoomBetween(Rect rc) {
		int x = 0;
		int y = 0;

		int width = (int) (m_map.getIntrinsicWidth());
		int height = (int) (m_map.getIntrinsicHeight());

		int swidth = (int) (width - getWidth());
		int sheight = (int) (height - getHeight());

		if (swidth <= 0)
			x = swidth / 2;
		else
			x = (int) Math.min(Math.max(0, x), swidth);

		if (sheight <= 0)
			y = sheight / 2;
		else
			y = (int) Math.min(Math.max(0, y), sheight);

		float scale = Math.max((float) width / (float) (rc.right - x),
				(float) height / (float) (rc.bottom - y));

		m_mapX = (int) (rc.left * scale);
		m_mapY = (int) (rc.top * scale);
		m_scale = scale;
		invalidate();
	}

	public interface StationSelectListener {
		void onStationSelected(Station station);
	}

	public static float distance(Point pt1, Point pt2) {
		return (float) Math.sqrt((pt1.x - pt2.x) * (pt1.x - pt2.x)
				+ (pt1.y - pt2.y) * (pt1.y - pt2.y));
	}

	public static double angle(Point a, Point b) {

		double dx = b.x - a.x;
		double dy = b.y - a.y;
		double angle = 0.0d;

		if (dx == 0.0) {
			if (dy == 0.0)
				angle = 0.0;
			else if (dy > 0.0)
				angle = Math.PI / 2.0;
			else
				angle = (Math.PI * 3.0) / 2.0;
		} else if (dy == 0.0) {
			if (dx > 0.0)
				angle = 0.0;
			else
				angle = Math.PI;
		} else {
			if (dx < 0.0)
				angle = Math.atan(dy / dx) + Math.PI;
			else if (dy < 0.0)
				angle = Math.atan(dy / dx) + (2 * Math.PI);
			else
				angle = Math.atan(dy / dx);
		}
		return (angle * 180) / Math.PI;
	}

	public static Point getPointOnLine(Point pt1, float degree, float dist) {
		double rad = Math.toRadians(degree);
		return new Point((int) (pt1.x + dist * Math.cos(rad)),
				(int) (pt1.y + dist * Math.sin(rad)));
	}

	static class TrainAnimationThread extends Thread {
		private Handler m_moving;
		private ArrayList<Station> m_way;
		private boolean m_needStop = false;

		public TrainAnimationThread(ArrayList<Station> way, Handler moving) {
			m_moving = moving;
			m_way = way;
		}

		public void stopAnimation() {
			m_needStop = true;
		}

		@Override
		public void run() {
			Point ptLast = null;

			for (int i = 0, length = m_way.size(); i < length && !m_needStop; i++) {
				Point pt = m_way.get(i).getSreenLocation();
				float dist = 0;

				if (ptLast != null) {
					float offset = 1; // 2px
					dist = distance(pt, ptLast);
					float degree = (float) angle(pt, ptLast);
					int count = (int) (dist / offset);
					dist = offset;
					Point ptt = ptLast;

					for (int j = 0; j < count - 1; j++) {
						ptt = getPointOnLine(ptLast, degree - 180, dist);
						m_moving.obtainMessage(0, ptt.x, ptt.y).sendToTarget();

						try {
							this.sleep(10);
						} catch (InterruptedException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}

						dist += offset;
					}

					ptLast = pt;
				} else {
					ptLast = pt;
					m_moving.obtainMessage(0, pt.x, pt.y).sendToTarget();
				}
			}

			try {
				this.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}

			m_moving.obtainMessage(1, 0, 0).sendToTarget();
			m_needStop = false;
		}
	}

	public void stopTutorial(ArrayList<Station> way) {
		if (train_anim != null)
			train_anim.stopAnimation();

		setVisibility(GONE);
	}
}
